package br.senai.sc.controller.crud;

import java.io.Serializable;
import java.util.List;
import java.util.Set;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;

import org.jboss.seam.international.status.Messages;
import org.jboss.seam.international.status.builder.BundleKey;

import br.senai.sc.controller.business.BaciaHidrograficaBusiness;
import br.senai.sc.controller.business.CorpoHidricoBusiness;
import br.senai.sc.controller.exception.BusinessException;
import br.senai.sc.model.Cidade;
import br.senai.sc.model.CorpoHidrico;
import br.senai.sc.util.SystemConstants;

/**
 * Responsável pelas funções que envolvem banco de dados da entidade
 * {@link CorpoHidrico}.
 * 
 * @author Tiago Wanke Marques &lt;tiago.marques@sc.senai.br&gt;
 */
public class CorpoHidricoCRUD implements Serializable {

	private static final long serialVersionUID = 1L;

	@PersistenceContext
	private EntityManager em;

	@Inject
	private BaciaHidrograficaBusiness baciaHidrograficaBusiness;

	@Inject
	private CorpoHidricoBusiness corpoHidricoBusiness;

	@Inject
	private Messages messages;

	@Inject
	private Validator validator;

	/**
	 * @param idCorpoHidrico
	 * @return {@link CorpoHidrico} a partir do id informado.
	 */
	public CorpoHidrico findCorpoHidrico(Integer idCorpoHidrico) {

		if (idCorpoHidrico == null) {
			throw new IllegalArgumentException(
					"Passado identificador null para procurar Corpo Hídrico.");
		}

		return em.find(CorpoHidrico.class, idCorpoHidrico);
	}

	/**
	 * @return Lista de {@link CorpoHidrico} cadastrados.
	 */
	public List<CorpoHidrico> findCorposHidricos() {
		return em.createNamedQuery(CorpoHidrico.CORPOS_HIDRICOS,
				CorpoHidrico.class).getResultList();
	}

	/**
	 * @param cidade
	 * @return Lista de {@link CorpoHidrico} da {@link Cidade} informada.
	 */
	public List<CorpoHidrico> findCorposHidricos(Cidade cidade) {

		if (cidade == null) {
			throw new IllegalArgumentException(
					"Informada cidade null para retornar corpos hidricos por cidade.");
		}

		return em
				.createNamedQuery(CorpoHidrico.CORPOS_HIDRICOS_BY_CIDADE,
						CorpoHidrico.class).setParameter("cidade", cidade)
				.getResultList();
	}

	/**
	 * Converte a lista de {@link ConstraintViolation} para uma lista de
	 * {@link BundleKey}.
	 * 
	 * @param constraints
	 * @return
	 */
	private void toBundleKeyList(
			Set<ConstraintViolation<CorpoHidrico>> constraints) {

		for (ConstraintViolation<CorpoHidrico> constraint : constraints) {
			messages.error(new BundleKey(
					SystemConstants.PATH_TO_COMMON_MESSAGES, constraint
							.getMessageTemplate()));
		}

	}

	/**
	 * Persiste os dados de um {@link CorpoHidrico}.
	 * 
	 * @see FR08 - Cadastrar Corpo Hídrico.
	 * @param corpoHidrico
	 * @return {@link CorpoHidrico} salvo.
	 * @throws BusinessException
	 *             Lançada caso houver algum erro de validação.
	 */
	public CorpoHidrico salvar(CorpoHidrico corpoHidrico)
			throws BusinessException {

		if (corpoHidrico == null) {
			throw new IllegalArgumentException(
					"Tentando salvar corpo hídrico informando null.");
		}

		Set<ConstraintViolation<CorpoHidrico>> constraints = validator
				.validate(corpoHidrico);
		if (constraints.size() > 0) {
			toBundleKeyList(constraints);
			throw new BusinessException();
		}

		if (corpoHidrico.getIdCorpoHidrico() == null) {
			inserir(corpoHidrico);
			return corpoHidrico;
		}

		return atualizar(corpoHidrico);
	}

	/**
	 * Caso não houver nenhum erro de validação, persiste a entidade.
	 * 
	 * @param corpoHidrico
	 * @throws BusinessException
	 *             Lançada caso houver algum erro de validação.
	 */
	private void inserir(CorpoHidrico corpoHidrico) throws BusinessException {

		if (!corpoHidricoBusiness.podeSalvar(corpoHidrico)) {
			throw new BusinessException();
		}

		em.persist(corpoHidrico);
	}

	/**
	 * Efetua as regras de negócio para atualizar {@link CorpoHidrico}.
	 * 
	 * @param corpoHidrico
	 * @return {@link CorpoHidrico} atualizado (attached).
	 * @throws BusinessException
	 *             Lançada caso houver algum erro de validação.
	 */
	private CorpoHidrico atualizar(CorpoHidrico corpoHidrico)
			throws BusinessException {

		if (!corpoHidricoBusiness.podeEditar(corpoHidrico)) {
			throw new BusinessException();
		}

		return em.merge(corpoHidrico);
	}

	/**
	 * Remove um {@link CorpoHidrico} do banco de dados. Este método efetua um
	 * merge na entidade antes de remove-la para garantir que a mesma esteja
	 * attached.
	 * 
	 * @param corpoHidrico
	 *            {@link CorpoHidrico} que será removida.
	 * @throws BusinessException
	 *             Lançada caso houver alguma validação que não seja atendida.
	 */
	public void remover(CorpoHidrico corpoHidrico) throws BusinessException {

		// a verificação de null não é feita aqui, pois o metodo existe já
		// garante esta verificação
		if (!corpoHidricoBusiness.podeRemover(corpoHidrico)) {
			throw new BusinessException();
		}

		em.remove(em.merge(corpoHidrico)); // garante que a entidade seja
											// attached

	}

}
